package java

import (
	"fmt"
	. "org.xx/kris2/common"
	"os"
	"path/filepath"
)

func WriteEntityMappersCode(rootPackage, destination string, dbEntities []*DbEntity) {
	c := 0
	for _, entity := range dbEntities {
		_, err := WriteEntityMapperCode(rootPackage, destination, entity)

		if err != nil {
			PrintError("%s", err)
		} else {
			c++
		}
	}
	PrintInfo("%d entity mapper(s) generated", c)
}

func WriteEntityMapperCode(rootPackage, destination string, entity *DbEntity) (string, error) {
	if entity == nil {
		panic("Argument \"entity\" must not be nil")
	}

	resetIndent()

	className := IdentToPascal(entity.Name) + "Mapper"

	entityClassName := formatFullName(getFullName(rootPackage, entity.Package, "domain", entity.Name))
	package_ := getFullName(rootPackage, entity.Package, "jdbc")

	dir := makePackageDir(destination, package_)
	srcName := filepath.Join(dir, className+".java")

	PrintVerbose("Generating entity: %s (%s) ...", className, srcName)

	f, err := os.Create(srcName)
	if err != nil {
		return "", fmt.Errorf("Can not open file %q for write\n  %s", srcName, err)
	}

	defer f.Close()

	var field *DbField
	var keyType DbFieldType
	keys := make([]*DbField, 0, len(entity.Fields))
	for _, field = range entity.Fields {
		if field.Key {
			keys = append(keys, field)
		}
	}
	if len(keys) == 0 {
		// 无法为未指定key的实体生成mapper
		panic(fmt.Sprintf("Cannot generate mapper for entity without key: %s", entity.Name))
	} else if len(keys) > 1 {
		// 无法为具有多个key的实体生成mapper
		panic(fmt.Sprintf("Cannot generate mapper for entity with multiple key: %s", entity.Name))
	} else {
		keyType = keys[0].Type
	}

	writeFileBriefDoc(f)

	writeCodeLine(f, "package %s;", package_)
	writeBlankLine(f)
	writeCodeLine(f, "import org.xx.armory.spring5.jdbc.EntityMapper;")
	writeBlankLine(f)

	writeCodeLine(f, "/**")
	writeCodeLine(f, " * Mapper of: %s", entity.Name)
	writeCodeLine(f, " * @see %s", getFullName(package_, entityClassName))
	writeCodeLine(f, " * <p>%s</p>", formatLineInfoComment(&entity.LineInfo))
	writeCodeLine(f, " */")
	writeCodeLine(f, "public class %s ", className)
	indent()
	indent()
	writeCodeLine(f, "extends EntityMapper<%s, %s> {", formatFieldType(keyType, field.EnumType, &field.LineInfo), entityClassName)
	unIndent()

	writeEntityMapperConstructor(f, entity, className)
	writeEntityMapperExecute(f, entity, entityClassName, className)

	unIndent()
	writeCodeLine(f, "}")

	return srcName, nil
}

func writeEntityMapperConstructor(f *os.File, entity *DbEntity, className string) {
	// 输出注释。
	writeCodeLine(f, "/**")
	writeCodeLine(f, " * Consturctor of %s", className)
	writeCodeLine(f, " */")

	// 输出方法头。
	writeCodeLine(f, "public %s() {", className)
	indent()

	// 输出方法结束。
	unIndent()
	writeCodeLine(f, "}")
	writeBlankLine(f)
	writeBlankLine(f)
}

func writeEntityMapperExecute(f *os.File, entity *DbEntity, entityClassName, className string) {
	// 输出方法头。
	writeCodeLine(f, "@Override")
	writeCodeLine(f, "protected %s extractEntity(", entityClassName)
	indent()
	indent()
	writeCodeLine(f, "java.sql.ResultSet resultSet")
	unIndent()
	unIndent()
	writeCodeLine(f, ") {")
	indent()

	writeCodeLine(f, "if (resultSet == null) {")
	indent()
	writeCodeLine(f, "throw new IllegalArgumentException(\"Argument \\\"resultSet\\\" must not be null\");")
	unIndent()
	writeCodeLine(f, "}")
	writeBlankLine(f)

	writeCodeLine(f, "return new %s(", entityClassName)
	indent()
	var maxIndex = len(entity.Fields) - 1
	var le string
	for index, field := range entity.Fields {
		if index < maxIndex {
			le = ","
		} else {
			le = ""
		}
		writeCodeLine(f, "%s%s", formatGetResultSetValue(field.Type, field.EnumType, index+1), le)
	}
	unIndent()
	writeCodeLine(f, ");")

	// 输出方法结束。
	unIndent()
	writeCodeLine(f, "}")
}

func formatGetResultSetValue(type_ DbFieldType, enumType string, index int) string {
	// TODO 允许设置IntegerEnum或者StringEnum。
	switch type_ {
	case FtString:
		return fmt.Sprintf("getString(resultSet, %d)", index)
	case FtInteger:
		return fmt.Sprintf("getLong(resultSet, %d)", index)
	case FtDecimal:
		return fmt.Sprintf("getBigDecimal(resultSet, %d)", index)
	case FtDouble:
		return fmt.Sprintf("getDouble(resultSet, %d)", index)
	case FtDateTime:
		return fmt.Sprintf("getDate(resultSet, %d)", index)
	case FtBoolean:
		return fmt.Sprintf("getBoolean(resultSet, %d)", index)
	case FtBinary:
		return fmt.Sprintf("getBytes(resultSet, %d)", index)
	case FtEnum:
		if enumType == "" {
			return "<UNKNOWN ENUM>"
		}
		return fmt.Sprintf("getEnum(resultSet, %s.class, %d)", formatFullName(enumType), index)
	default:
		return "<UNKNOWN>"
	}
}
